//#pragma once

#ifndef CCOMMON_H
#define CCOMMON_H

#include <iostream>
#include <mutex>
#include <assert.h>

#ifdef _WIN32
#include <Windows.h>
#else //Linux
#include <sys/mman.h>
#include <unistd.h>
#include <stdexcept>
#endif 


static const size_t MAX_BYTES  = 256 * 1024;//256KB
static const size_t NFREELISTS = 208;		//桶的总数
static const size_t NPAGES     = 129;		//页的数量
static const size_t PAGE_SHIFT = 13;		// 1 << 13 = 8192 为一个页的大小




//去堆上按页申请内存
inline static void* SystemAlloc(size_t kpage)
{
	void* ptr = nullptr;
#ifdef _WIN32
	ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE,
		PAGE_READWRITE);

#else //Linux

	/*
	  void* addr：这是映射的起始地址，通常使用 nullptr 表示由系统选择合适的地址。这样可以避免地址冲突和增加映射成功的机会。
	  size_t length：这是映射的内存区域的长度，以字节为单位。
	  int prot：这是保护模式，用于指定内存区域的保护级别。PROT_READ | PROT_WRITE 表示区域可读可写，允许读取和写入。
	  int flags：这是标志，用于指定映射的类型和属性。MAP_PRIVATE | MAP_ANONYMOUS 表示创建一个私有的、匿名的映射，这意味着映射不与文件关联，且对进程的其他部分不可见。
	  int fd：这是文件描述符，如果映射文件而不是匿名映射，则指定要映射的文件的描述符。在这里，使用 -1 表示不映射任何文件。
	  off_t offset：如果映射文件，这是文件中的偏移量。在这里，偏移量为0，表示从文件的开头开始映射。
	*/
	ptr = mmap(nullptr, kpage << 13, PROT_READ | PROT_WRITE,
		MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

	if (ptr == MAP_FAILED)
	{
		ptr = nullptr;
	}

#endif

	return ptr;
}


//释放堆区内存
static void SystemFree(void* ptr, size_t kpage)
{
#ifdef _WIN32
	if (ptr != nullptr)
	{
		VirtualFree(ptr, 0, MEM_RELEASE);
	}
#else
	
	if (ptr != nullptr && kpage > 0)
	{
		if (munmap(ptr, kpage << 13) == -1)
		{
			assert(false);
		}
	}

#endif
}

//从头部截取对象一个指针的大小 并返回
//用于存储下一块内存的首地址
static void*& NextObj(void* obj)
{
	return *(void**)obj;
}


//管理切分好的小对象的自由链表
class FreeList
{
public:
	//头插一块内存
	void Push(void* obj);
	//弹出对象 头删
	void* Pop();

	//头插一段链表
	//start: 指向链表头
	//end:   指向链表尾
	//n:	 链表节点数量
	void PushRange(void* start, void* end, size_t n);

	//头删一段链表
	//start: 指向链表头
	//end:   指向链表尾
	//n:	 链表节点数量
	void PopRange(void*& start, void*& end, size_t n);


	bool Empty() { return m_freeList == nullptr; }
	size_t GetMaxSize() { return m_maxSize; }
	void SetMaxSize(size_t size) { m_maxSize = size; }
	size_t Size() { return m_size; }

private:
	void* m_freeList = nullptr;	//链表头指针
	size_t m_maxSize = 1;		//当链表的节点达到m_maxSize时，就还一段内存给central cache
	size_t m_size = 0;			//当前链表节点数量
};


//计算对象大小的对齐映射规则
class SizeClass
{
public:

	/*
	* 根据对象大小的对齐映射规则 计算内存大小
	* size:   用户要开辟的内存大小
	* return：在内存池中实际开辟的大小
	*/
	static size_t RoupdUp(size_t size);

	//计算映射到哪一个自由链表通
	static size_t Index(size_t size);

	//thread cache 一次从中心缓存获取多少个对象
	static size_t NumMoveSize(size_t size);

	// 计算一次向系统获取几个页
	static size_t NumMovePage(size_t size);

private:
	/*
	根据对象大小的对齐映射规则 计算内存大小，整体控制在最多10%左右的内碎片浪费
	[1,128] 8byte对齐					 freelist[0,16)
	[128+1,1024] 16byte对齐				 freelist[16,72)
	[1024+1,8*1024] 128byte对齐			 freelist[72,128)
	[8*1024+1,64*1024] 1024byte对齐		 freelist[128,184)
	[64*1024+1,256*1024] 8*1024byte对齐  freelist[184,208)
	*/
	/*
	* bytes:	要开辟的内存大小
	* alignNum：字节对齐规则
	* return：  字节对齐后要开辟的内存大小
	*/
	static size_t _RoundUp(size_t bytes, size_t alignNum);



	/*
	[1,128]								 每隔8字节开一个桶
	[128+1,1024]						 每隔16字节开一个桶
	[1024+1,8*1024]						 每隔128字节开一个桶
	[8*1024+1,64*1024]					 每隔1024字节开一个桶
	[64*1024+1,256*1024]				 每隔8192(8 * 1024)字节开一个桶
	*/
	//计算映射到哪一个自由链表通
	static size_t _Index(size_t bytes, size_t alignNum);
};


//管理多个连续大块内存跨度结构
struct Span
{
	Span* next = nullptr;
	Span* prev = nullptr;

	size_t pageId	 = 0;	   //大块内存起始页号
	size_t n		 = 0;	   //页的数量
	size_t objSize   = 0;	   //切好的小内存大小
	size_t useCount  = 0;	   //切好的小块内存，被分给thread cache计数
	void*  freeList  = nullptr;//切好的小块内存自由链表头指针
	bool   isUsed	 = false;  //是否被使用
};


//带头的双向链表
class SpanList
{
public:
	SpanList();

	//获取链表第一个有效节点
	Span* Begin();

	//获取链表最后一个节点
	Span* End();

	//判读链表是否为空
	bool Empty();

	//头插
	void PushFront(Span* span);

	//头删
	Span* PopFront();

	void Erase(Span* pos);

public:
	std::mutex m_mtx; //桶锁
private:
	Span* m_head; //链表头
};


#endif 